import copy
from collections.abc import Iterable

from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
from rest_framework import serializers

from sysreptor.pentests.models.files import UploadedFileBase
from sysreptor.utils.history import bulk_create_with_history


class ExportImportSerializer(serializers.ModelSerializer):
    def export_files(self, instance) -> Iterable[tuple[str, File]]:
        return []


class MultiFormatSerializer(serializers.Serializer):
    serializer_formats: dict[str, ExportImportSerializer] = {}
    export_format = None

    def __init__(self, *args, **kwargs):
        self.serializer_formats = copy.deepcopy(self.serializer_formats)
        if not self.serializer_formats:
            raise ImproperlyConfigured(f'{self.__class__.__name__}: No format serializers defined')
        if self.export_format:
            if self.export_format not in self.serializer_formats:
                raise ImproperlyConfigured(f'{self.__class__.__name__}: export_format "{self.export_format}" not in format_serializers')
        else:
            self.export_format = self.export_format or list(self.serializer_formats.keys())[0]

        super().__init__(*args, **kwargs)
        for s in self.serializer_formats.values():
            s.bind('', self)

    @property
    def export_serializer(self):
        return self.serializer_formats[self.export_format]

    def run_validation(self, data):
        data_format = (data or {}).get('format')
        if not data_format:
            raise serializers.ValidationError({'format': 'Missing format field'})
        serializer = self.serializer_formats.get(data_format)
        if not serializer:
            supported_formats = ' or '.join(map(lambda f: f'"{f}"', self.serializer_formats.keys()))
            raise serializers.ValidationError({'format': f'Invalid format: expected {supported_formats} got "{data_format}"'})
        return serializer.run_validation(data=data) | {
            'format': data_format,
        }

    def create(self, validated_data):
        return self.serializer_formats[validated_data.pop('format')].create(validated_data)

    def update(self, instance, validated_data):
        return self.serializer_formats[validated_data.pop('format')].update(instance, validated_data)

    def to_representation(self, *args, **kwargs):
        return self.export_serializer.to_representation(*args, **kwargs) | {
            'format': self.export_format,
        }

    def export_files(self, *args, **kwargs):
        return self.export_serializer.export_files(*args, **kwargs)


class FileListExportImportSerializer(serializers.ListSerializer):
    def export_files(self, instance):
        for f in instance:
            if self.child.is_file_referenced(f):
                yield from self.child.export_files(instance=f)

    def to_representation(self, data):
        return super().to_representation([f for f in data.all() if self.child.is_file_referenced(f)])

    def extract_file(self, name):
        return self.context['archive'].extractfile(self.child.get_path_in_archive(name))

    def create(self, validated_data):
        child_model_class = self.child.get_model_class()
        objs = [
            child_model_class(**attrs | {
                'name_hash': UploadedFileBase.hash_name(attrs['name']),
                'file': File(
                    file=self.extract_file(attrs.pop('name_internal', None) or attrs['name']),
                    name=attrs['name']),
                'linked_object': self.child.get_linked_object(),
        }) for attrs in validated_data]

        bulk_create_with_history(child_model_class, objs)
        self.context['storage_files'].extend(map(lambda o: o.file, objs))
        return objs


class FileExportImportSerializer(ExportImportSerializer):
    class Meta:
        fields = ['id', 'created', 'updated', 'name']
        extra_kwargs = {
            'id': {'read_only': True},
            'created': {'read_only': False, 'required': False},
        }
        list_serializer_class = FileListExportImportSerializer

    def get_model_class(self):
        return self.Meta.model

    def validate_name(self, name):
        if '/' in name or '\\' in name or '\x00' in name:
            raise serializers.ValidationError(f'Invalid filename: {name}')
        return name

    def get_linked_object(self):
        pass

    def get_path_in_archive(self, name):
        pass

    def is_file_referenced(self, f):
        return self.get_linked_object().is_file_referenced(f)

    def export_files(self, instance) -> Iterable[tuple[str, File]]:
        yield self.get_path_in_archive(instance.name), instance.file
